// Copyright 2020 smileschool.com
// leovs @2020.8.28

package client

import (
	"encoding/json"
	"fmt"
	"github.com/go-resty/resty/v2"
	"github.com/xuanbo/requests"
	"go.uber.org/zap"
	"math/rand"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"sync"
	"time"
	eureka "zl-gin/eureka-client"
	"zl-gin/utils"
)

//处理url正则
const UrlReg = `^(http|https):\/\/([^/]+)\/([^?]*)(.*)`

// 接口请求超时时间
const TimeOut = 2 * time.Second

type Client struct {
	eureka *eureka.Client
	client *resty.Client
}

type Url struct {
	Protocol   string
	ServerName string
	ServerPort int
	Path       string
	Param      string
}

var (
	instance *Client
	once     sync.Once
)

func GetInstance() *Client {
	once.Do(func() {
		instance = &Client{}
	})
	return instance
}

//url地址转换成Url对象
func (c *Client) formatUrl(url string) *Url {
	result := &Url{}
	comp := regexp.MustCompile(UrlReg)
	sabbaths := comp.FindAllStringSubmatch(url, -1)
	// 取协议
	result.Protocol = sabbaths[0][1]

	// 获取服务名和端口号
	serName := strings.Split(sabbaths[0][2], ":")
	result.ServerName = serName[0]
	if len(serName) == 2 {
		result.ServerPort = utils.String2Int(serName[1])
	} else {
		result.ServerPort = 80
	}

	// 取参数
	result.Path = sabbaths[0][3]
	if len(sabbaths[0]) > 4 {
		result.Param = sabbaths[0][4]
	}
	return result
}

//从eureka中找到服务地址
func (c *Client) FindEurekaServer(serverName string) *eureka.Instance {
	if c.eureka.Applications != nil && c.eureka.Applications.Applications != nil {
		for _, app := range c.eureka.Applications.Applications {
			if strings.EqualFold(app.Name, serverName) {
				// 尝试随机取一个可用服务
				inst := app.Instances[rand.Intn(len(app.Instances))]
				if strings.EqualFold(inst.Status, "UP") {
					return &inst
				}
				// 随机取不可用时，轮询取一个可用的服务
				for _, inst := range app.Instances {
					if strings.EqualFold(inst.Status, "UP") {
						return &inst
					}
				}
				zap.L().Error("[EurekaServer] 服务状态已失效 %v \n", zap.String("app.name", app.Name))
			}
		}
	}
	return nil
}

//运行eureka
func (c *Client) RunEureka(defaultZone, app, version string, port int) {
	c.eureka = eureka.NewClient(&eureka.Config{
		DefaultZone:           defaultZone,
		App:                   app,
		Port:                  port,
		RenewalIntervalInSecs: 10,
		DurationInSecs:        30,
		Metadata: map[string]interface{}{
			"VERSION":              version,
			"NODE_GROUP_ID":        0,
			"PRODUCT_CODE":         "DEFAULT",
			"PRODUCT_VERSION_CODE": "DEFAULT",
			"PRODUCT_ENV_CODE":     "DEFAULT",
			"SERVICE_VERSION_CODE": "DEFAULT",
		},
	}).Start()
}

func (c *Client) changeUrl(url string) (error, string) {
	urlInfo := c.formatUrl(url)
	inst := c.FindEurekaServer(urlInfo.ServerName)
	if inst == nil {
		return fmt.Errorf("服务不存在: %s", url), ""
	}
	return nil, strings.ReplaceAll(url, urlInfo.ServerName, fmt.Sprintf("%v:%v", inst.HostName, inst.Port.Port))
}

// Get请求
func (c *Client) get(url string, params url.Values, v interface{}) error {
	err, newUrl := c.changeUrl(url)
	if err != nil {
		request := requests.Request(newUrl, http.MethodGet, &http.Client{Timeout: TimeOut})
		return request.Params(params).Send().StatusOk().Json(v)
	}
	return err
}

//Post请求
func (c *Client) post(url string, params url.Values, body interface{}, v interface{}) error {
	err, newUrl := c.changeUrl(url)
	if err == nil {
		request := requests.Request(newUrl, http.MethodPost, &http.Client{Timeout: TimeOut})
		raw, err2 := request.Params(params).Json(body).Send().StatusOk().Raw()
		if err2 != nil || raw == nil {
			return err2
		}
		zap.L().Error(fmt.Sprintf("接口调用 Url[%v] Content[%v]\n", newUrl, string(raw)))
		return json.Unmarshal(raw, &v)
	}
	return nil
}

//Post请求
func (c *Client) head(url string, params http.Header, v interface{}) error {
	err, newUrl := c.changeUrl(url)
	if err == nil {
		request := requests.Request(newUrl, http.MethodPost, &http.Client{Timeout: TimeOut})
		raw, err2 := request.Header("Accept", "application/json").Headers(params).Send().StatusOk().Raw()
		if err2 != nil || raw == nil {
			return err2
		}
		zap.L().Error(fmt.Sprintf("接口调用 Url[%v] Content[%v]\n", url, string(raw)))
		return json.Unmarshal(raw, &v)
	}
	return nil
}

//Post请求
func (c *Client) headString(url string, params http.Header) (string, error) {
	err, newUrl := c.changeUrl(url)
	if err == nil {
		request := requests.Request(newUrl, http.MethodPost, &http.Client{Timeout: TimeOut})
		raw, err2 := request.Headers(params).Send().StatusOk().Text()
		if err2 != nil {
			return "", err2
		}
		zap.L().Error(fmt.Sprintf("接口调用 Url[%v] Content[%v]\n", newUrl, raw))
		return raw, nil
	}
	return "", nil
}
